package com.donfu.system.service.house.impl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.*;

import com.donfu.common.exception.user.BusinessErrorCode;
import com.donfu.common.exception.user.BusinessException;
import com.donfu.system.domain.dto.FileChunkDTO;
import com.donfu.system.domain.dto.FileChunkResultDTO;
import com.donfu.system.domain.house.HouseAboutModen;
import com.donfu.system.mapper.house.HouseAboutModenMapper;
import com.donfu.system.service.house.IHouseAboutModenService;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.util.HtmlUtils;

import javax.annotation.Resource;

/**
 * 关于摩登文章Service业务层处理
 * 
 * @author donfu
 * @date 2021-12-08
 */

@Service
public class HouseAboutModenServiceImpl implements IHouseAboutModenService
{

    private Logger logger= LoggerFactory.getLogger(HouseAboutModenServiceImpl.class);
    @Autowired
    private HouseAboutModenMapper houseAboutModenMapper;

    @Resource
    private RedisTemplate<String,Object> redisTemplate;



    @Value("${donfu.profile}")
    private String profile;

    /**
     * 查询关于摩登文章
     * 
     * @param id 关于摩登文章主键
     * @return 关于摩登文章
     */
    @Override
    public HouseAboutModen selectHouseAboutModenById(Long id)
    {
        HouseAboutModen houseAboutModen = houseAboutModenMapper.selectHouseAboutModenById(id);
        // 对文章内容进行反转译
        String content = houseAboutModen.getContent();
        String s = HtmlUtils.htmlUnescape(content);
        houseAboutModen.setContent(s);
        return houseAboutModen ;
    }

    /**
     * 查询关于摩登文章列表
     * 
     * @param houseAboutModen 关于摩登文章
     * @return 关于摩登文章
     */
    @Override
    public List<HouseAboutModen> selectHouseAboutModenList(HouseAboutModen houseAboutModen)
    {
        List<HouseAboutModen> list = houseAboutModenMapper.selectHouseAboutModenList(houseAboutModen);
        return list;
    }

    /**
     * 新增关于摩登文章
     * 
     * @param houseAboutModen 关于摩登文章
     * @return 结果
     */
    @Override
    public int insertHouseAboutModen(HouseAboutModen houseAboutModen)
    {
        houseAboutModen.setCreateDate(new Date());
        return houseAboutModenMapper.insertHouseAboutModen(houseAboutModen);
    }

    /**
     * 修改关于摩登文章
     * 
     * @param houseAboutModen 关于摩登文章
     * @return 结果
     */
    @Override
    public int updateHouseAboutModen(HouseAboutModen houseAboutModen)
    {
        return houseAboutModenMapper.updateHouseAboutModen(houseAboutModen);
    }

    /**
     * 批量删除关于摩登文章
     * 
     * @param ids 需要删除的关于摩登文章主键
     * @return 结果
     */
    @Override
    public int deleteHouseAboutModenByIds(Long[] ids)
    {
        return houseAboutModenMapper.deleteHouseAboutModenByIds(ids);
    }

    /**
     * 删除关于摩登文章信息
     * 
     * @param id 关于摩登文章主键
     * @return 结果
     */
    @Override
    public int deleteHouseAboutModenById(Long id)
    {
        return houseAboutModenMapper.deleteHouseAboutModenById(id);
    }

    /**
     * 检查文件是否存在，如果存在则跳过该文件的上传，如果不存在，返回需要上传的分片集合
     *
     * @param chunkDTO
     * @return
     */
    @Override
    @SuppressWarnings("all")
    public FileChunkResultDTO checkChunkExist(FileChunkDTO chunkDTO) throws BusinessException {
        //1.检查文件是否已上传过
        //1.1)检查在磁盘中是否存在
        String fileFolderPath = getFileFolderPath(chunkDTO.getIdentifier());
        String filePath = getFilePath(chunkDTO.getIdentifier(), chunkDTO.getFilename());
        chunkDTO.setFilePath(filePath);
        File file = new File(filePath);
        boolean exists = file.exists();
        //1.2)检查Redis中是否存在,并且所有分片已经上传完成。
        Set<Integer> uploaded = (Set<Integer>) redisTemplate.opsForHash().get(chunkDTO.getIdentifier(), "uploaded");
        if (uploaded != null && uploaded.size() == chunkDTO.getTotalChunks() && exists) {
            return new FileChunkResultDTO(true);
        }
        File fileFolder = new File(fileFolderPath);
        if (!fileFolder.exists()) {
            boolean mkdirs = fileFolder.mkdirs();
            logger.info("准备工作,创建文件夹,fileFolderPath:{},mkdirs:{}", fileFolderPath, mkdirs);
        }
        return new FileChunkResultDTO(false, uploaded);
    }


    /**
     * 上传分片
     *
     * @param chunkDTO
     */
    @Override
    public void uploadChunk(FileChunkDTO chunkDTO) throws BusinessException {
        //分块的目录
        String chunkFileFolderPath = getChunkFileFolderPath(chunkDTO.getIdentifier());
        File chunkFileFolder = new File(chunkFileFolderPath);
        if (!chunkFileFolder.exists()) {
            boolean mkdirs = chunkFileFolder.mkdirs();
            logger.info("创建分片文件夹:{}", mkdirs);
        }
        //写入分片
        try (
                InputStream inputStream = chunkDTO.getFile().getInputStream();
                FileOutputStream outputStream = new FileOutputStream(new File(chunkFileFolderPath + chunkDTO.getChunkNumber()))
        ) {
            IOUtils.copy(inputStream, outputStream);
            logger.info("文件标识:{},chunkNumber:{}", chunkDTO.getIdentifier(), chunkDTO.getChunkNumber());
            //将该分片写入redis
            long size = saveToRedis(chunkDTO);
            //合并分片
            if (size == chunkDTO.getTotalChunks()) {
                File mergeFile = mergeChunks(chunkDTO.getIdentifier(), chunkDTO.getFilename());
                if (mergeFile == null) {
                    throw new BusinessException(BusinessErrorCode.INVALID_PARAMETER, "合并文件失败");
                }
            }
            String filePath = getFilePath(chunkDTO.getIdentifier(), chunkDTO.getFilename());
            chunkDTO.setFilePath(filePath);
        } catch (Exception e) {
            throw new BusinessException(BusinessErrorCode.INVALID_PARAMETER);
        }
    }

    /**
     * 合并分片
     *
     * @param identifier
     * @param filename
     */
    private File mergeChunks(String identifier, String filename) throws BusinessException {
        String chunkFileFolderPath = getChunkFileFolderPath(identifier);
        String filePath = getFilePath(identifier, filename);
        File chunkFileFolder = new File(chunkFileFolderPath);
        File mergeFile = new File(filePath);
        File[] chunks = chunkFileFolder.listFiles();
        try {
            RandomAccessFile randomAccessFileWriter = new RandomAccessFile(mergeFile, "rw");
            // 分块列表
            File[] filesArray = chunkFileFolder.listFiles();

            long startTime = System.currentTimeMillis();
            // 转为集合
            List<File> fileList = new ArrayList<>(Arrays.asList(filesArray));
            // 合并需要从小到大排序
            fileList.sort(new Comparator<File>() {
                @Override
                public int compare(File o1, File o2) {
                    if (Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())) {
                        return -1;
                    }
                    return 1;
                }
            });
            // 缓冲区
            byte[] bytes = new byte[1024];
            // 向分块文件中写数据
            RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");
            // 合并文件
            for (File chunkFile : fileList) {

                RandomAccessFile raf_read = new RandomAccessFile(chunkFile, "rw");
                int len = -1;
                while ((len = raf_read.read(bytes)) != -1) {
                    raf_write.write(bytes, 0, len);
                }
                raf_read.close();
            }
            raf_write.close();
            // 删除分片文件夹
//            FileUtils.deleteDirectory(mergeFile);
            long endTime = System.currentTimeMillis();
//            System.out.println("合并的消耗的时间为:" + (endTime - startTime) / 1000 + "秒");
        }catch (Exception e) {
            throw new BusinessException(BusinessErrorCode.INVALID_PARAMETER);
        }
        return mergeFile;
    }

    /**
     * 分片写入Redis
     *
     * @param chunkDTO
     */
    @SuppressWarnings("all")
    private synchronized long saveToRedis(FileChunkDTO chunkDTO) {
        Set<Integer> uploaded = (Set<Integer>) redisTemplate.opsForHash().get(chunkDTO.getIdentifier(), "uploaded");
        if (uploaded == null) {
            uploaded = new HashSet<>(Arrays.asList(chunkDTO.getChunkNumber()));
            HashMap<String, Object> objectObjectHashMap = new HashMap<>();
            objectObjectHashMap.put("uploaded", uploaded);
            objectObjectHashMap.put("totalChunks", chunkDTO.getTotalChunks());
            objectObjectHashMap.put("totalSize", chunkDTO.getTotalSize());
            objectObjectHashMap.put("path", getFileRelativelyPath(chunkDTO.getIdentifier(), chunkDTO.getFilename()));
            redisTemplate.opsForHash().putAll(chunkDTO.getIdentifier(), objectObjectHashMap);
        } else {
            uploaded.add(chunkDTO.getChunkNumber());
            redisTemplate.opsForHash().put(chunkDTO.getIdentifier(), "uploaded", uploaded);
        }
        return uploaded.size();
    }

    /**
     * 得到文件的绝对路径
     *
     * @param identifier
     * @param filename
     * @return
     */
    private String getFilePath(String identifier, String filename) {
        String ext = filename.substring(filename.lastIndexOf("."));
        return getFileFolderPath(identifier) + identifier + ext;
    }

    /**
     * 得到文件的相对路径
     *
     * @param identifier
     * @param filename
     * @return
     */
    public String getFileRelativelyPath(String identifier, String filename) {
        Calendar cal = Calendar.getInstance();
        int year = cal.get(Calendar.YEAR);//获取年份
        int month=cal.get(Calendar.MONTH)+ 1 ;//获取月份
        int day=cal.get(Calendar.DATE);//获取日
        String ext = filename.substring(filename.lastIndexOf("."));
        return "/" +"video/" + year + "/" +
                month + "/" +
                day + "/" +
                identifier + "/" + identifier
                + ext;
    }


    /**
     * 得到分块文件所属的目录
     *
     * @param identifier
     * @return
     */
    private String getChunkFileFolderPath(String identifier) {
        return getFileFolderPath(identifier) + "chunks" + File.separator;
    }

    /**
     * 得到文件所属的目录
     *
     * @param identifier
     * @return
     */
    private String getFileFolderPath(String identifier) {
        Calendar cal = Calendar.getInstance();
        int year = cal.get(Calendar.YEAR);//获取年份
        int month=cal.get(Calendar.MONTH)+1;//获取月份
        int day=cal.get(Calendar.DATE);//获取日
        return profile +"/"+"video/" +
                year + File.separator +
                month + File.separator +
                day + File.separator +
                identifier + File.separator;
    }


}
